feat(schematics): rewrite Vertex AI imports to AI Logic on ng update - #3725
Conversation
AngularFire 21 renamed the Vertex AI module to Firebase AI Logic: the @angular/fire/vertexai and older @angular/fire/vertexai-preview entry points were removed in favor of @angular/fire/ai, and the exported symbols were renamed (getVertexAI to getAI, provideVertexAI to provideAI, VertexAI to AI, and so on). A workspace on 20 that used Vertex AI would fail to compile after the upgrade. Extend the existing v21 migration (which aligns the firebase dependency) to also rewrite these imports and their usages. The rewrite parses each source file with the TypeScript compiler and edits only genuine references, so it leaves strings, comments, and unrelated identifiers that merely share a name untouched. It handles named imports and their aliases, namespace imports in both value and type position, bare local re-exports, and shorthand properties. The one accepted limitation is name shadowing: because the rewrite matches by name, a local variable that shadows an imported name with the same spelling can be mis-renamed. ng update always presents its changes as a diff for review, so this is caught on inspection. Also add typescript to the schematics esbuild externals so the compiler is resolved from the workspace at ng-update time rather than bundled into the package, matching how Angular's own migrations ship. Docs: add a 20-to-21 upgrade guide, note the rename in the AI Logic guide, and link the upgrade guide from the README. Refs angular#3686
tyler-reitz
left a comment
There was a problem hiding this comment.
Nice work on the AST approach — the two-pass design holds up well under poking. The namespace and identifier passes can't double-edit the same token (isMemberOrDeclaredName catches the child visit in both value and type position), applyEdits sorting back-to-front is right, and the spec coverage is genuinely thorough: shorthand expansion, accessor look-alikes, destructuring keys vs. binding initializers, idempotency. *.jasmine.ts is picked up by tools/jasmine.ts, so these run in CI. The symbol map matches the real src/ai/public_api.ts exports for every entry except one — which is the blocker below.
1. getVertexAI → getAI is not a rename, and the rewrite silently changes which backend the app calls
getVertexAI and getAI both existed in the old module. git show ac3dd7c^:src/vertexai/firebase.ts exports all four of getAI, getVertexAI, getGenerativeModel, getImagenModel. Two coexisting functions aren't a rename pair.
getAI() with no options defaults to the Google AI (Gemini Developer API) backend; the Vertex equivalent is getAI(app, { backend: new VertexAIBackend() }). So a user on Vertex who runs ng update ends up with code that compiles and then talks to a different API, with different enablement and billing. Nothing in the diff would look wrong on review, which defeats the "ng update always shows its changes as a diff" safety net cited under Known limitation.
Two ways out:
- rewrite
getVertexAI(...)togetAI(app, { backend: new VertexAIBackend() }), addingVertexAIBackendto the rewritten import, or - leave
getVertexAIcalls alone andcontext.logger.warnwith a pointer to the upgrade guide.
Worth confirming the SDK's default backend against @firebase/ai before picking. The symbol table in docs/version-21-upgrade.md and the note added to docs/ai.md need the same correction — as written, both teach the rename as safe.
2. export { VertexAI } from '@angular/fire/vertexai' renames the user's public export
src/schematics/update/v21/vertexai-to-ai.ts:181 — collectSpecifierEdit rewrites importedNameNode, which is element.name when there's no propertyName. For a re-export with a from clause that produces export { AI } from '@angular/fire/ai', so the file's external export name changes from VertexAI to AI and downstream consumers break.
identifierUsageEdit already handles this correctly for the bare local re-export — expanding to export { AI as VertexAI }, with a good comment explaining why. The from-clause path should do the same. (export { VertexAI as Foo } from '...' is already correct, since propertyName is set.)
3. The migration walks node_modules when a project has no sourceRoot
rewriteVertexAIToAI derives roots from sourceRoot || root. A root project with root: "" and no sourceRoot — which older CLI versions generate — gives posix.join('/', '') → /, and every path in the tree passes the prefix test. The content.includes(specifier) prefilter keeps most files from being parsed, but any dependency that re-exports @angular/fire/vertexai gets rewritten in place inside node_modules.
A filePath.includes('/node_modules/') guard is the usual fix and costs nothing.
4. Step ordering interacts badly with the typescript external
Making typescript an esbuild external is the right call for package size, but it isn't declared anywhere in src/package.json — unlike firebase-tools, which is an optional peer. Under a strict or isolated node_modules layout the top-level import * as ts fails at module load, which takes down the whole migration.
Because src/schematics/update/v21/index.ts:14 runs the rewrite before alignFirebaseVersion, that failure also costs users the firebase-12 alignment — the part they genuinely can't do without. Two small changes: declare typescript as an optional peer dependency, and run alignFirebaseVersion first so a rewrite failure can only cost you the rewrite.
5. Not covered: direct firebase/vertexai imports
Since this migration also moves users to Firebase JS SDK 12, where that entry point is gone, direct SDK imports break too. The guide flags it as manual, but the same AST pass would handle it with one more entry in OLD_MODULE_SPECIFIERS — worth considering, subject to the same getVertexAI caveat above.
Merge order
This is stacked on unmerged predecessor work, and it edits the same README feature-table region as #3724 (that PR re-flows the row boundaries around the last two cells; this one rewrites the Vertex AI cell's content). Whichever lands second will need a manual rebase.
tyler-reitz
left a comment
There was a problem hiding this comment.
Marking this as changes-requested to hold the merge, per my earlier review.
The blocker is item 1: getVertexAI and getAI coexisted in the old module (git show ac3dd7c^:src/vertexai/firebase.ts exports both), so rewriting one to the other isn't a rename — it moves a Vertex user onto the Google AI backend silently, and the resulting diff looks correct on inspection. That needs resolving in the migration and in both docs pages before this lands.
Items 2-4 (the export { X } from public-export rename, the node_modules walk when sourceRoot is absent, and the typescript external / step-ordering interaction) are smaller but concrete. Item 5 is optional.
Happy to re-review once the backend question is settled.
getAI and getVertexAI coexisted in the old vertexai module and default
to different backends: plain getAI() talks to the Gemini Developer API,
so rewriting getVertexAI as a plain rename silently moved Vertex users
onto another Google API. Rewritten calls now become
getAI(app, { backend: new VertexAIBackend(location?) }), and every
rewritten site is logged with its file and line.
Anything the migration cannot rewrite with identical semantics is left
in place with a per-site warning, and the moved import path then fails
to compile, so nothing changes behavior silently. That covers
non-literal options, getVertexAI handed around as a value or
re-exported, local declarations that shadow a rewritten name, getAI or
VertexAIBackend bound from a non AI Logic source, star re-exports,
files with syntax errors, and removed symbols with no drop-in
successor (VertexAIOptions).
Also addressed from review: an un-aliased export { X } from an old
entry point keeps the file's public export name via an alias, a root
project without a sourceRoot is walked (with a node_modules guard),
and typescript is now an optional peer resolved from the workspace at
update time, loaded after the firebase alignment so a resolution
failure costs only the rewrite. Direct firebase/vertexai imports
migrate under the same rules.
The migration is reorganized from one file into
schematics/update/v21/vertexai-to-ai/ (rename tables, shared
interfaces, compiler resolution, safety analyses, the two scan passes,
edit builders, orchestration). applyEdits is exported only so its
edit-conflict guard is unit-testable, and ngUpdate's optional compiler
parameter exists for the ESM test run, where require is unavailable.
|
Thanks Tyler. Item 1 caught something real, and the response grew well past the five items, so here is each one and then a summary of what else changed. 1. getVertexAI backend semanticsYou were right, and I confirmed it against the SDK source before changing anything:
While auditing the old module's export surface for this, I also added 2. Public export names in re-exportsFixed as you suggested: an un-aliased 3. The node_modules walkI tried to reproduce this before fixing it, and what I found is a different failure than we both expected. A
4. typescript as an externalAdopted both suggestions, with one addition I found while testing them.
5. Direct firebase/vertexai importsAdopted. Beyond the five itemsYour item 1 generalized into the rule the whole migration now follows:
The single file was getting long, so it is now a small module directory under This is a much bigger delta than the review asked for, so take whatever time it needs. If anything looks off, or you would rather see part of it split out, say the word. |
tyler-reitz
left a comment
There was a problem hiding this comment.
Approving. I pulled the branch and verified rather than taking the summary on faith: npm run build succeeds, ng lint is clean apart from the pre-existing @ts-ignore in deploy/actions.ts, and the node suite is 117 specs / 0 failures, matching your numbers (60 in vertexai-to-ai.jasmine.ts plus 4 in index.jasmine.ts).
On item 3, you are right and my diagnosis was wrong. The original .filter((base: string) => !!base) drops '' before it reaches posix.join, so a root: "" project never produced a / root, and your second point holds too: startsWith(root + '/') builds '//' for a / root and matches nothing. There was no node_modules walk. The real bug was the inverse of what I described, a root project silently skipped and never migrated, and you found and fixed it. Your !filePath.split('/').includes('node_modules') is also better than the .includes('/node_modules/') I suggested, and it is genuinely load-bearing now that / is a legal root.
Item 1 checks out end to end. I confirmed options?.backend ?? new GoogleAIBackend() in @firebase/ai myself, so the original rewrite really did move callers onto the wrong backend. One detail your summary does not mention that I checked, because it decides whether the rewrite is truly behavior preserving: legacy @firebase/vertexai used DEFAULT_LOCATION = 'us-central1' and VertexAIBackend's constructor defaults to the same value, so getVertexAI(app) becoming getAI(app, { backend: new VertexAIBackend() }) keeps the region identical. The zero-argument case is right for the same kind of reason, since getAI's signature is getAI(app = getApp(), options) and an explicit undefined triggers the default parameter. Items 2, 4, and 5 all match what you describe in the code.
The "nothing changes behavior silently" rule is the right one to have landed on, and the per-site warnings plus the moved import path make the failures loud in the place the user needs to look.
One observation, not a request: this grew from roughly 460 lines to 2,394, and a good part of that is hardening beyond the blocker. It is the right hardening and I am not asking you to unpick it now. But a change that arrives at this size is hard to review as a unit, and next time the safety analyses and the optional item 5 work would probably be easier on a reviewer as a follow-up on top of the core fix. Worth keeping in mind rather than acting on here.
Adds an
ng updatemigration that moves a workspace off the Vertex AI module onto Firebase AI Logic, and a guide for upgrading from AngularFire 20 to 21.Background
AngularFire 21 renamed the Vertex AI module to Firebase AI Logic. The
@angular/fire/vertexaientry point (and the older@angular/fire/vertexai-preview) were removed in favor of@angular/fire/ai, and the exported symbols were renamed. A project upgrading from 20 that used Vertex AI would fail to compile until it updated those imports by hand.What this does
firebasedependency) to also rewrite Vertex AI imports and their usages to AI Logic. It parses each source file with the TypeScript compiler and edits only real references, leaving strings, comments, and look-alike identifiers untouched. Named imports and aliases, namespace imports in value and type position, re-exports, and shorthand properties are handled.getVertexAIis not treated as a rename.getAIcoexisted with it in the old module, and plaingetAI()defaults to the Gemini Developer API backend, so rewritten calls becomegetAI(app, { backend: new VertexAIBackend(location?) })and keep the caller on the Vertex AI backend. Every rewritten site is logged with its file and line.firebase/vertexaiimports (that entry point is also gone in SDK 12) migrate under the same rules.docs/version-21-upgrade.md, a note indocs/ai.md, and a README link.Symbol map
@angular/fire/vertexai)@angular/fire/ai)getVertexAI(app?, { location? })getAI(app, { backend: new VertexAIBackend(location?) })provideVertexAIprovideAIVertexAIAIVertexAIErrorAIErrorVertexAIErrorCodeAIErrorCodeVertexAIModelAIModelVertexAIInstancesAIInstancesvertexAIInstance$AIInstance$VertexAIModuleAIModulegetGenerativeModelandgetImagenModelkeep their names.VertexAIOptionswas removed rather than renamed (the newAIOptionstakes abackendinstead of alocation), so imports of it are left in place and warned about.Left for manual migration (each site gets its own warning)
getVertexAIcalls whose arguments are not an optional app plus an optional literal{ location }object, or whose options mention other rewritten symbolsgetVertexAIhanded around as a value or re-exported (rewriting either would silently change which backend its callers reach)getAIorVertexAIBackendalready bound from a source other than AI Logic (the rewrite cannot inject or reuse them safely)export * froman old entry point (rewriting it would silently rename the file's re-exported public API)A file where a named
getVertexAIimport has any unrewritable use keeps every use of its namedgetVertexAIimports, so the pieces stay consistent. Namespace-stylens.getVertexAI(...)calls are judged per call.Design notes
schematics/update/v21/vertexai-to-ai/by responsibility: rename tables, shared interfaces, compiler resolution, safety analyses, the two scan passes, the getVertexAI edit builders, and orchestration.typescriptis a new optional peer dependency (>=5.8 <6.0), kept an esbuild external so the package does not grow by several megabytes, and resolved from the workspace when the rewrite first needs it (package resolution, then a workspace-root fallback for isolated layouts). It loads after the firebase alignment, so an unresolvable compiler costs only the rewrite and logs a warning. Verified in an environment with no typescript resolvable anywhere, and under pnpm's isolated node_modules layout.applyEditsis exported only so its edit-conflict guard is unit-testable.ngUpdate's optionalcompilerparameter exists for the ESM test run, whererequireis unavailable.Verification
getVertexAIcall form, each left-for-manual case above, root handling (a root project with nosourceRoot, trailing-slash roots,node_modulesexclusion, a null project entry), dedup of injected imports, log message content, deep-expression files, and an idempotent second run. I mutation-tested the guards: re-breaking them makes specs fail.@angular/fire/vertexai, installed the packed tarball, and ranng update @angular/fire --migrate-only. The imports and the call rewrote (thelocationoption moved intoVertexAIBackend),firebasealigned to^12.4.0, and a second run made no changes.Refs #3686